package com.elnguage.tool;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;

public class GenerateAst
{
    public static void main(String[] args) throws IOException{
        if(args.length != 1)
        {
            System.err.println("Failed to parse args. Usage: generate_ast <output_dir>");
            System.exit(64);
        }
        String outputDir = args[0];
        defineAst(outputDir, "Expr", Arrays.asList(
            "Assign     :   Token name,Expr value",
            "Binary     :   Expr left,Token operator,Expr right",
            "Call       :   Expr callee,Token paren,List<Expr> arguments",
            "Grouping   :   Expr expression",
            "Literal    :   Object value",
            "Logical    :   Expr left,Token operator,Expr right",
            "Unary      :   Token operator,Expr right",
            "Variable   :   Token name",
            "EventCat   :   Expr prev,Expr cosq" 
        ));

        defineAst(outputDir, "Stmt", Arrays.asList(
           "Block       :   List<Stmt> statements",
           "Expression  :   Expr expression",
           "Function    :   Token name,List<Token> params," +"List<Stmt> body",
           "If          :   Expr condition,Stmt thenBranch," + "Stmt elseBranch",
           "Print       :   Expr expression",
           "Var         :   Token name,Expr initializer",
           "While       :   Expr condition,Stmt body"
        ));

        defineAst(outputDir, "StellarisStmt", Arrays.asList(
           "Block       :   List<StellarisStmt> statements",
           "Event       :   Token name,List<Stmt> body",    //用于定义事件
           "Handle      :   Token name,List<Stmt> body",    //用于定义事件句柄
           "Call        :   Token name,List<StellarisStmt> params"
        ));

        defineAst(outputDir, "BaseStmt", Arrays.asList(
            "StellaStmt :   StellarisStmt stella_stmt",
            "BasicStmt  :   Stmt statement"
        ));
    }
    private static void defineAst(String outputDir, String baseName, List<String> types) throws IOException
    {
        String path = outputDir + "/" + baseName + ".java";
        PrintWriter writer = new PrintWriter(path, "UTF-8");

        writer.println("package com.elnguage.lox;");
        writer.println();
        writer.println("import java.util.List;");
        writer.println();
        writer.println("abstract class " + baseName + "{");
        
        //define visitor interface
        defineVisitor(writer, baseName, types);

        //define sub expression types
        for(String type : types)
        {
            String className = type.split(":")[0].trim();
            String fields = type.split(":")[1].trim();
            defineType(writer, baseName, className, fields);
        }

        writer.println();
        writer.println("  abstract <R> R accept(Visitor<R> visitor);");

        writer.println("}");
        writer.close();
    }
    private static void defineType(PrintWriter writer, String baseName, String className, String fields) throws IOException
    {
        //define fields
        String[] fieldList = fields.split(",");
        writer.println("static class " + className + " extends " + baseName + "{");
        writer.println(className + "(" + fields + ") {");
        for(String f : fieldList)
        {
            writer.println("this." + f.split(" ")[1] + " = " + f.split(" ")[1] + ";");
        }
        writer.println("}");
        for(String f : fieldList)
        {
            writer.println("final " + f + ";");
        }
        //define implementation of accept
        writer.println();
        writer.println("@Override");
        writer.println("<R> R accept(Visitor<R> visitor){");
        writer.println("return visitor.visit" + className + baseName + "(this);");
        writer.println("}");
        writer.println("}");

        
    }
    private static void defineVisitor(PrintWriter writer, String baseName, List<String> types)
    {
        writer.println("  interface Visitor<R> {");
        
        for(String type : types)
        {
            String typeName = type.split(":")[0].trim();
            writer.println("    R visit" + typeName + baseName + "(" + typeName + " " + baseName.toLowerCase() + ");");
        }
        writer.println("  };");
    }
}